/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
 *
 */

import { SpawnSyncOptionsWithBufferEncoding } from 'child_process';
import * as fs from 'fs';
import fse from 'fs-extra';
import os from 'os';
import * as path from 'path';
import process from 'process';
import { gte, lt } from 'semver';

import { DEFAULT_HVIGOR_CONFIG_JSON_FILE_NAME, DEFAULT_PACKAGE_JSON, NPM_TOOL, PROJECT_CACHES, WORK_SPACE } from '../common/const/const.js';
import { HVIGOR_PROJECT_ROOT_DIR, HVIGOR_PROJECT_WRAPPER_HOME } from '../common/const/path-const.js';
import { HvigorConfigJson, PackageJson } from '../common/type.js';
import { isCI,hash } from '@ohos/hvigor-common';
import { parseJsonFile } from '../common/util/parse-json/parse-json-util.js';

import { logErrorAndExit, logFormatedError, logFormatedErrorAndExit, logInfo} from './log.js';
import { checkNpmConifg } from './pnpm.js';
import { BASE_NODE_VERSION, executeCommand, hasNpmPackInPaths, isHvigorDependencyUseNpm, offlinePluginConversion } from './util.js';
import { HVIGOR_PNPM_STORE_PATH, HVIGOR_USER_HOME, HVIGOR_WRAPPER_PNPM_SCRIPT_PATH } from './wapper-path-const.js';
import { exit } from '../base/boot/hooks/exit.js';

const version = require('../../package.json').version;
let curProjectHvigorConfigJson: HvigorConfigJson | undefined;
let curProjectPackageJson: PackageJson | undefined;
let hvigorProjectHome: string;
const currentNodeVersion = process.version.slice(1);

export function initProjectWorkSpace(): string {
  curProjectHvigorConfigJson = readProjectHvigorConfig();
  hvigorProjectHome = calProjectCacheDir();
  curProjectPackageJson = readProjectPackageJson();

  // hvigor未安装，或依赖未完全安装则清理工作空间，重新执行插件安装流程
  if (!isAllDependenciesInstalled()) {
    try {
      checkNpmConifg();
      executeInstallHvigor();
    } catch (e) {
      cleanWorkSpace();
    }
  }

  linkHvigorToWorkspace(hvigorProjectHome);
  return hvigorProjectHome;
}

const IS_WINDOWS = process.platform === 'win32' || os.type() === 'Windows_NT';

export function linkHvigorToWorkspace(projectHome: string) {
  const dir = path.resolve(__dirname, '..');
  const dest = path.resolve(projectHome, 'node_modules', '@ohos');
  const symlinkType = IS_WINDOWS ? 'junction' : 'dir';
  try {
    fse.ensureDirSync(dest);
    if (!curProjectHvigorConfigJson?.dependencies['@ohos/hvigor']) {
      doSymlink(path.resolve(dest, 'hvigor'), path.resolve(dir, 'hvigor'), symlinkType);
    }
    if (!curProjectHvigorConfigJson?.dependencies['@ohos/hvigor-ohos-plugin']) {
      doSymlink(path.resolve(dest, 'hvigor-ohos-plugin'), path.resolve(dir, 'hvigor-ohos-plugin'), symlinkType);
    }
    if (!curProjectHvigorConfigJson?.dependencies['@ohos/cangjie-build-support']) {
      doCangjieSymbolic(dir, dest, symlinkType);
    }
  } catch (e) {
    logErrorAndExit(e);
  }
}

function doSymlink(target: string, dir: string, symlinkType: 'junction' | 'dir') {
  try {
    if (!fs.existsSync(target)) {
      // write
      fs.symlinkSync(dir, target, symlinkType);
      return;
    }

    const realPath = path.resolve(fs.readlinkSync(target));
    if (!fs.lstatSync(target).isSymbolicLink() || realPath !== dir) {
      // rewrite
      fs.rmSync(target, { recursive: true, force: true });
      fs.symlinkSync(dir, target, symlinkType);
      return;
    }

    // check version
    const pkg = parseJsonFile(path.resolve(realPath, 'package.json')) as PackageJson;
    if (pkg.version !== version) {
      // rewrite
      fs.rmSync(target, { recursive: true, force: true });
      fs.symlinkSync(dir, target, symlinkType);
    }
  } catch (e) {
    fs.rmSync(target, { recursive: true, force: true });
    fs.symlinkSync(dir, target, symlinkType);
  }
}

/**
 * 是否使用新的workspace dir算法
 */
function calProjectCacheDir() {
  // 只要依赖里面有一个本地离线插件就不使用新算法
  return path.resolve(HVIGOR_USER_HOME, PROJECT_CACHES, hash(path.resolve(process.cwd())), WORK_SPACE);
}

function isAllDependenciesInstalled(): boolean {
  function getObjDependenciesFieldNums(obj: PackageJson | HvigorConfigJson | undefined) {
    const objDependencies = obj?.dependencies;
    return objDependencies === undefined ? 0 : Object.getOwnPropertyNames(objDependencies).length;
  }

  const projectHvigorConfigJsonDependenciesNums = getObjDependenciesFieldNums(curProjectHvigorConfigJson);

  const projectPackageJsonDependenciesNums = getObjDependenciesFieldNums(curProjectPackageJson);

  // package.json中的dependencies的数据包含了hvigor本身,因此需要hvigor-config.json的数据+1做比较
  if (projectHvigorConfigJsonDependenciesNums !== projectPackageJsonDependenciesNums) {
    return false;
  }

  for (const pkgName in curProjectHvigorConfigJson?.dependencies) {
    if (!hasNpmPackInPaths(pkgName, [hvigorProjectHome]) || !isSameDependencyVersion(pkgName, curProjectHvigorConfigJson!, curProjectPackageJson!)) {
      return false;
    }
  }
  return true;
}

function isSameDependencyVersion(dependencyName: string, hvigorConfigJson: HvigorConfigJson, packageJson: PackageJson) {
  if (packageJson.dependencies === undefined) {
    return false;
  }
  return offlinePluginConversion(process.cwd(), hvigorConfigJson.dependencies![dependencyName]) === path.normalize(packageJson.dependencies[dependencyName]);
}

function configPnpmStoreDir() {
  const configPnpmStoreDirCmdParams = ['config', 'set', 'store-dir', HVIGOR_PNPM_STORE_PATH];
  fse.ensureDirSync(hvigorProjectHome);
  const pnpmExecuteCommandOptions: SpawnSyncOptionsWithBufferEncoding = {
    cwd: hvigorProjectHome,
    stdio: ['inherit', 'inherit', 'inherit'],
  };
  executeCommand(HVIGOR_WRAPPER_PNPM_SCRIPT_PATH, configPnpmStoreDirCmdParams, pnpmExecuteCommandOptions);
}

function install() {
  const installCmdParams = ['install'];

  if (isCI()) {
    installCmdParams.push('--no-frozen-lockfile');
  }
  if (fse.existsSync(path.resolve(HVIGOR_PROJECT_ROOT_DIR, '.npmrc'))) {
    process.env['npm_config_userconfig'] = concatenateNpmrc(path.resolve(HVIGOR_PROJECT_ROOT_DIR, '.npmrc'));
  }
  const pnpmExecuteCommandOptions: SpawnSyncOptionsWithBufferEncoding = {
    cwd: hvigorProjectHome,
    stdio: ['inherit', 'inherit', 'inherit'],
    env: process.env,
  };

  if (isHvigorDependencyUseNpm() || lt(currentNodeVersion, BASE_NODE_VERSION)) {
    // npm
    const workspaceHvigorPath = path.resolve(hvigorProjectHome, 'node_modules/@ohos/hvigor');
    const workspaceHvigorOhosPluginPath = path.resolve(hvigorProjectHome, 'node_modules/@ohos/hvigor-ohos-plugin');
    fs.existsSync(workspaceHvigorPath) && fs.unlinkSync(workspaceHvigorPath);
    fs.existsSync(workspaceHvigorOhosPluginPath) && fs.unlinkSync(workspaceHvigorOhosPluginPath);

    executeCommand(NPM_TOOL, installCmdParams, { cwd: hvigorProjectHome });
    linkHvigorToWorkspace(hvigorProjectHome);
  } else {
    // pnpm
    executeCommand(HVIGOR_WRAPPER_PNPM_SCRIPT_PATH, installCmdParams, pnpmExecuteCommandOptions);
  }
}

function concatenateNpmrc(projectNpmPath: string) {
  const result = path.resolve(path.dirname(hvigorProjectHome), '.npmrc');
  try {
    let userNpm = '';
    const userNpmPath = path.resolve(os.homedir(), '.npmrc');
    if (fse.existsSync(userNpmPath)) {
      userNpm = fse.readFileSync(userNpmPath, 'utf-8');
    }
    const proNpm = fse.readFileSync(projectNpmPath, 'utf-8');
    const resultNpm = `${userNpm}\n${proNpm}`;
    fse.ensureFileSync(result);
    fse.writeFileSync(result, resultNpm);
  } catch (e) {
    logErrorAndExit(e);
  }
  return result;
}

function executeInstallHvigor() {
  logInfo('Installing dependencies...');
  const pnpmfile = path.resolve(hvigorProjectHome, '.pnpmfile.js');
  if (fse.existsSync(pnpmfile)) {
    fse.rmSync(pnpmfile, { force: true });
  }

  // 支持离线包路径转换
  for (const [key, val] of Object.entries(curProjectHvigorConfigJson?.dependencies ?? {})) {
    if (!val) {
      continue;
    }
    curProjectHvigorConfigJson!.dependencies![key] = offlinePluginConversion(process.cwd(), val);
  }

  // add dependencies
  const generatePackageJson: PackageJson = {
    dependencies: {
      ...curProjectHvigorConfigJson!.dependencies,
    },
  };

  try {
    fs.mkdirSync(hvigorProjectHome, { recursive: true });
    const projectDependencyPackageJsonPath = path.resolve(hvigorProjectHome, DEFAULT_PACKAGE_JSON);
    fs.writeFileSync(projectDependencyPackageJsonPath, JSON.stringify(generatePackageJson));
  } catch (e) {
    logErrorAndExit(e);
  }

  if (!isHvigorDependencyUseNpm() && gte(currentNodeVersion, BASE_NODE_VERSION)) {
    configPnpmStoreDir();
  }
  install();
  logInfo('Hvigor install success.');
}

/**
 * 读取用户工程目录下的hvigor-config.json5
 *
 * @returns {HvigorConfigJson} hvigor-config.json5
 */
export function readProjectHvigorConfig(): HvigorConfigJson | undefined {
  const hvigorConfigJsonPath = path.resolve(HVIGOR_PROJECT_WRAPPER_HOME, DEFAULT_HVIGOR_CONFIG_JSON_FILE_NAME);

  // hvigorw端强依赖此文件，hvigor-config.json5不存在则报错
  if (!fs.existsSync(hvigorConfigJsonPath)) {
    logFormatedErrorAndExit( '00304004', `Hvigor config file ${hvigorConfigJsonPath} does not exist.`, ['Check whether the hvigor-config.json5 file exists.']);
  }
  let json;
  try {
    json = parseJsonFile(hvigorConfigJsonPath) as HvigorConfigJson;
    json.dependencies = json.dependencies ?? {};
  } catch (e) {
    if (e instanceof Error) {
      let errorMsg = `${e.message}`;
      if (process.argv.includes('--stacktrace') && e.stack) {
        errorMsg += `${e.stack}`;
      }
      let solution:string[] = ['Correct the syntax error as indicated above in the hvigor-config.json5 file.'];
      logFormatedError('00303232', errorMsg, solution);
      exit(-1);
    }
  }
  return json;
}

/**
 * 读取用户工程对应的./hvigor/project_caches目录中缓存的package.json
 *
 * @returns {PackageJson} package.json
 */
function readProjectPackageJson(): PackageJson {
  const hvigorProjectDependecyPackageJsonPath = path.resolve(hvigorProjectHome, DEFAULT_PACKAGE_JSON);
  if (fs.existsSync(hvigorProjectDependecyPackageJsonPath)) {
    return parseJsonFile(hvigorProjectDependecyPackageJsonPath) as PackageJson;
  } else {
    return {
      dependencies: {},
    };
  }
}

/**
 * 清理workspace
 */
function cleanWorkSpace(): void {
  logInfo('Hvigor cleaning...');
  if (!fs.existsSync(hvigorProjectHome)) {
    return;
  }

  const filenames: string[] = fs.readdirSync(hvigorProjectHome);
  if (!filenames || filenames.length === 0) {
    return;
  }

  const HVIGOR_BOOT_JS_FILE_PATH = path.resolve(hvigorProjectHome, 'node_modules', '@ohos', 'hvigor', 'bin', 'hvigor.js');

  // 关闭deamon 进程
  if (fs.existsSync(HVIGOR_BOOT_JS_FILE_PATH)) {
    executeCommand(process.argv[0], [HVIGOR_BOOT_JS_FILE_PATH, '--stop-daemon'], {});
  }

  // 清理workspace
  try {
    filenames.forEach((filename) => {
      fs.rmSync(path.resolve(hvigorProjectHome, filename), { recursive: true });
    });
  } catch (e) {
    logErrorAndExit(
      'The hvigor build tool cannot be installed. Please manually clear ' +
        `the workspace directory and synchronize the project again.\n
      Workspace Path: ${hvigorProjectHome}.`,
    );
  }
}

/**
 * 添加仓颉链接
 */
function doCangjieSymbolic(dir: string, dest: string, symlinkType: 'junction' | 'dir'): void {
  const cangjiePath = process.env.DEVECO_CANGJIE_PATH;
  if (cangjiePath !== undefined && fs.existsSync(cangjiePath)) {
    const cangjieBuildPath = path.resolve(cangjiePath, 'compiler', 'tools', 'hvigor', 'cangjie-build-support');
    if (fs.existsSync(cangjieBuildPath)) {
      doSymlink(path.resolve(dest, 'cangjie-build-support'), cangjieBuildPath, symlinkType);
      return;
    }
  }
  const defaultCangjieBuildPath = path.resolve(dir, '../../plugins/cangjie/hvigor/cangjie-build-support');
  if (fs.existsSync(defaultCangjieBuildPath)) {
    doSymlink(path.resolve(dest, 'cangjie-build-support'), defaultCangjieBuildPath, symlinkType);
  }
}
